﻿using System;
using System.Collections.Generic;
using GeneticAlgorithms.Genomes;
using GeneticAlgorithms.Populations;

namespace GeneticAlgorithms.Operators.Selection
{
    /// <summary>
    /// Implements selection through ranking.
    /// </summary>
    /// <typeparam name="TGenome">The type of the genome.</typeparam>
    /// <typeparam name="TGene">The type of the gene.</typeparam>
    public class RankingSelector<TGenome, TGene> : IGeneticSelector<TGenome, TGene> where TGenome : IGenome<TGene>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="RankingSelector&lt;TGenome, TGene&gt;"/> class.
        /// </summary>
        /// <param name="childrenPerRank">The children per rank.</param>
        public RankingSelector(IList<int> childrenPerRank)
        {
            this.childrenPerRank = childrenPerRank;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RankingSelector&lt;TGenome, TGene&gt;"/> class.
        /// </summary>
        public RankingSelector()
        {
        }

        private IList<int> childrenPerRank;

        #region IGeneticSelector<TGene> Members

        /// <summary>
        /// Selects the genomes.
        /// </summary>
        /// <param name="population">
        /// The population from which the selected genomes will be picked.
        /// </param>
        /// <param name="selectCount">The count of genomes to select.</param>
        /// <returns>A genome of selected genomes.</returns>
        public IEnumerable<TGenome> SelectGenomes(
            IPopulation<TGenome, TGene> population,
            int selectCount)
        {
            if (childrenPerRank == null)
            {
                CreateRankArray(population.Count);
            }
            int remaining = selectCount;

            population.Sort();

            for (int popPos = 0; popPos < childrenPerRank.Count; ++popPos)
            {
                for (int i = 0; i < childrenPerRank[popPos]; ++i)
                {
                    if (remaining <= 0)
                    {
                        break;
                    }

                    yield return population[popPos];
                    remaining--;
                }
                popPos++;
            }
        }

        #endregion

        private const double FirstPercentage = .25;
        private const double DecreaseFactor = .7;

        /// <summary>
        /// Creates the rank array.
        /// </summary>
        /// <param name="populationSize">Size of the population.</param>
        private void CreateRankArray(int populationSize)
        {
            childrenPerRank = new List<int>();
            int remaining = populationSize;
            int currentValue = (int)Math.Ceiling(FirstPercentage * populationSize);
            remaining -= currentValue;
            childrenPerRank.Add(currentValue);
            while (remaining > 0)
            {
                currentValue = (int)Math.Min(
                    Math.Ceiling(currentValue * DecreaseFactor),
                    remaining);
                childrenPerRank.Add(currentValue);
                remaining -= currentValue;
            }
        }
    }
}
